/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.linkbubble.httpseverywhere; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabaseCorruptException; import android.database.sqlite.SQLiteException; import android.support.annotation.NonNull; import android.util.JsonReader; import android.database.sqlite.SQLiteDatabase; import com.linkbubble.R; import com.linkbubble.adblock.ADBlockUtils; import com.linkbubble.util.CrashTracking; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; public class HttpsEverywhere { private static final String ETAG_PREPEND_RULE_SETS = "rs"; private static final String ETAG_PREPEND_TARGETS = "targ"; private static final int REDIRECT_BLACK_LIST_COUNT = 5; public HttpsEverywhere(Context context) { mVerNumber = ADBlockUtils.getDataVerNumber(context.getString(R.string.https_everywhere_rulesets_url)); ADBlockUtils.readData(context, context.getString(R.string.https_everywhere_rulesets_localfilename), context.getString(R.string.https_everywhere_rulesets_url), ETAG_PREPEND_RULE_SETS, mVerNumber, true); byte[] targets = ADBlockUtils.readData(context, context.getString(R.string.https_everywhere_targets_localfilename), context.getString(R.string.https_everywhere_targets_url), ETAG_PREPEND_RULE_SETS, mVerNumber, false); mTargets = new ConcurrentHashMap<String, List>(); if (null != targets) { ParseJSONObject(new ByteArrayInputStream(targets)); } String dbName = context.getApplicationInfo().dataDir + "/" + mVerNumber + context.getString(R.string.https_everywhere_rulesets_localfilename); try { mDB = SQLiteDatabase.openDatabase(dbName, null, SQLiteDatabase.OPEN_READONLY); } catch (SQLiteException e) { e.printStackTrace(); mDB = null; } mContext = context; } private void ParseJSONObject(InputStream in) { try { JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8")); readJSON(reader); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } private void readJSON(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); int value = 0; reader.beginArray(); List values = new ArrayList(); while (reader.hasNext()) { values.add(reader.nextInt()); } reader.endArray(); mTargets.put(name, values); } reader.endObject(); } public String getRealUrl(String originalUrl) { String host = ""; String protocol = ""; String path = ""; try { URL url = new URL(originalUrl); host = url.getHost(); protocol = url.getProtocol(); if (protocol.equals("https")) { return originalUrl; } path = originalUrl.substring(protocol.length() + "://".length() + host.length()); } catch (Exception e) { return originalUrl; } String[] domainParts = host.split(Pattern.quote(".")); if (domainParts.length <= 1) { return originalUrl; } StringBuilder domainToCheck = new StringBuilder(domainParts[domainParts.length - 1]); String ruleIds = ""; for (int i = domainParts.length - 2; i >= 0; i--) { domainToCheck.insert(0, "."); domainToCheck.insert(0, domainParts[i]); String prefix = ""; if (i > 0) { prefix = "*."; } List values = mTargets.get(prefix + domainToCheck); if (null == values) { continue; } for (int j = 0; j < values.size(); j++) { if (0 != ruleIds.length()) { ruleIds += ", "; } ruleIds += values.get(j).toString(); } } if (0 == ruleIds.length()) { return originalUrl; } try { String newHost = getNewHostFromIds(ruleIds, protocol + "://" + host); if (0 != newHost.length()) { // Temp fix for thestar.com, it will be fixed and remove on next https release if (newHost.startsWith("https://thestar.com")) { newHost = newHost.replace("https://", "https://www."); } return newHost + path; } } catch (SQLiteDatabaseCorruptException exc) { String dbName = mContext.getApplicationInfo().dataDir + "/" + mVerNumber + mContext.getString(R.string.https_everywhere_rulesets_localfilename); long length = -1; try { File file = new File(dbName); length = file.length(); } catch (NullPointerException e) { } catch (SecurityException e) { } String toLog = "File " + dbName + ", size == " + length; CrashTracking.log(toLog); CrashTracking.logHandledException(exc); } return originalUrl; } private String getNewHostFromIds(String ruleIds, String originalUrl) { if (null == mDB) { return ""; } Cursor cursor = mDB.rawQuery("select contents from rulesets where id in (" + ruleIds + ")", null); if (0 == cursor.getCount()) { return ""; } List<String> results = new ArrayList<String>(); while (cursor.moveToNext()) { results.add(cursor.getString(0)); } if (0 == results.size()) { return ""; } String newUrl = ""; for (String result: results) { try { JsonReader reader = new JsonReader(new InputStreamReader(new ByteArrayInputStream(result.getBytes()), "UTF-8")); reader.beginObject(); while (reader.hasNext()) { if (!reader.nextName().equals("ruleset")) { break; } reader.beginObject(); while (reader.hasNext()) { String topName = reader.nextName(); if (topName.equals("$")) { reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); if (name.equals("default_off") || name.equals("platform")) { break; } reader.skipValue(); } reader.endObject(); } else if (topName.equals("exclusion")) { reader.beginArray(); while (reader.hasNext()) { reader.beginObject(); if (!reader.nextName().equals("$")) { break; } reader.beginObject(); while (reader.hasNext()) { if (!reader.nextName().equals("pattern")) { break; } if (originalUrl.matches(".*" + reader.nextString() + ".*")) { return newUrl; } } reader.endObject(); reader.endObject(); } reader.endArray(); } else if (topName.equals("rule")) { reader.beginArray(); while (reader.hasNext()) { reader.beginObject(); if (!reader.nextName().equals("$")) { break; } reader.beginObject(); String from = ""; String to = ""; while (reader.hasNext()) { String fromToName = reader.nextName(); if (fromToName.equals("from")) { from = reader.nextString(); } else if (fromToName.equals("to")) { to = reader.nextString(); } else { reader.skipValue(); } } reader.endObject(); reader.endObject(); if (0 != from.length() && 0 != to.length() && originalUrl.matches(".*" + from + ".*")) { newUrl = originalUrl.replaceAll(from, to); if (newUrl.equals(originalUrl)) { newUrl = ""; } else { return newUrl; } } } reader.endArray(); } else { reader.skipValue(); } } reader.endObject(); } reader.endObject(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return newUrl; } private String mVerNumber; private ConcurrentHashMap<String, List> mTargets; private SQLiteDatabase mDB; private Context mContext; }